﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.ESB.Adapter;
using Microsoft.Practices.ESB.Exception.Management;
using Microsoft.Practices.ESB.GlobalPropertyContext;
using Microsoft.Practices.ESB.Itinerary;
using Microsoft.Practices.ESB.Itinerary.Services;
using Microsoft.Practices.ESB.Resolver;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Streaming;
using System.IO;
using System.Reflection;
using System.Xml;

namespace BizTalk.ESB.Extensions.MessagingServices
{
    public class CopyContextToContent : IMessagingService
    {
        IBaseMessage IMessagingService.Execute(IPipelineContext context, IBaseMessage msg, string resolverString, IItineraryStep step)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (msg == null)
                throw new ArgumentNullException("msg");
            if (string.IsNullOrEmpty(resolverString))
                throw new ArgumentException("Resolver must be provided", "resolverString");
            try
            {
                ResolverInfo info = ResolverMgr.GetResolverInfo(ResolutionType.Endpoint, resolverString);
                if (!info.Success)
                    throw new RoutingException("Invalid Resolver", resolverString);

                // Resolve configuration for routing.
                Dictionary<string, string> resolverDictionary = ResolverMgr.Resolve(info, msg, context);

                // Get Message as XmlDocument
                XmlDocument xmlMessage = new XmlDocument();
                xmlMessage.LoadXml(this.GetMessageContent(context, msg));

                // FOR EACH context property in resolver, add to message content
                // Use XPATH provided in resolver dictionary to replace current node value with context property value
                foreach (KeyValuePair<String,String> item in resolverDictionary)
                {
                    String propName, propNS;
                    String[] propParts = item.Key.Split(".".ToCharArray());
                    propName = propParts[0];
                    propNS = propParts[1];
                    String propValue = ((String)msg.Context.Read(propName, propNS));
                    XmlNode node = xmlMessage.SelectSingleNode(item.Value);
                    if (node != null)
                    {
                        node.Value = propValue;
                    }
                }

                // Copy Message with new content and return
                return CopyMessage(msg, context, xmlMessage.OuterXml);
            }
            catch (System.Exception ex)
            {
                EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
                throw;
            }    
        }

        string IMessagingService.Name
        {
            get { return "BizTalk.Core.Itinerary.MessagingServices.CopyContextToContent"; }
        }

        bool IMessagingService.ShouldAdvanceStep(IItineraryStep step, IBaseMessage msg)
        {
            return true;
        }

        bool IMessagingService.SupportsDisassemble
        {
            get { return false; }
        }

        private String GetMessageContent(IPipelineContext pContext, IBaseMessage message)
        {
            String msgData = string.Empty;
            Stream outStream = new VirtualStream();
            StreamWriter strmWriter = new StreamWriter(outStream);

            using (StreamReader sr = new
                          StreamReader(message.BodyPart.GetOriginalDataStream()))
            {
                msgData = sr.ReadToEnd();
                strmWriter.Flush(); // flush writer to ensure writing's done.
                strmWriter.Close(); // close stream since it won't be used
                sr.Close();
                return msgData;
            }
        }

        private IBaseMessage CopyMessage(IBaseMessage originalMessage, IPipelineContext pipelineContext, String messageContent)
        {
            Byte[] msgData = Encoding.ASCII.GetBytes(messageContent);
            Stream outStream = new MemoryStream(msgData);
            IBaseMessage msgCopy = this.CopyMessage(originalMessage, pipelineContext, outStream);
            return msgCopy;
        }

        private IBaseMessage CopyMessage(IBaseMessage originalMessage, IPipelineContext pipelineContext, Stream messageContent)
        {
            EventLogger.Write("Copying Message...");
            EventLogger.Write("Original Message Part Count: " + originalMessage.PartCount.ToString());

            // Message
            IBaseMessage msgCopy = pipelineContext.GetMessageFactory().CreateMessage();

            // Message Parts
            for (Int32 i = 0; i < originalMessage.PartCount; i++)
            {
                String partName = String.Empty;
                IBaseMessagePart part = originalMessage.GetPartByIndex(i, out partName);
                IBaseMessagePart partCopy = pipelineContext.GetMessageFactory().CreateMessagePart();
                EventLogger.Write("Copying message part " + partName);

                // Copy data stream
                messageContent.Seek(0, SeekOrigin.Begin);
                partCopy.Data = messageContent;
                pipelineContext.ResourceTracker.AddResource(partCopy.Data);

                // Copy properties
                partCopy.PartProperties = PipelineUtil.CopyPropertyBag(part.PartProperties, pipelineContext.GetMessageFactory());

                // Add part to message
                msgCopy.AddPart(partName, partCopy, (String.Compare(originalMessage.BodyPartName, partName, true, System.Globalization.CultureInfo.CurrentCulture) == 0));
            }

            // Message Context
            msgCopy.Context = PipelineUtil.CloneMessageContext(originalMessage.Context);
            EventLogger.Write("Message Copy complete...");
            EventLogger.Write("Copied Message Part Count: " + msgCopy.PartCount.ToString());

            return msgCopy;
        }
    }
}
